package com.atguigu.gmall.product.service.impl;

import com.atguigu.gmall.product.service.TestService;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;


    @Override
    public String readLock() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("lock");
        RLock rLock = lock.readLock();
        rLock.lock(10, TimeUnit.SECONDS);
        String msg = this.redisTemplate.opsForValue().get("msg");

//        rLock.unlock(); // 解锁
        return msg;
    }

    @Override
    public String writeLock() {
        RReadWriteLock lock = redissonClient.getReadWriteLock("lock");
        RLock rLock = lock.writeLock();
        rLock.lock(10, TimeUnit.SECONDS);
        redisTemplate.opsForValue().set("msg", UUID.randomUUID().toString());
//        rLock.unlock();
        return "成功写入了内容。。。。。。";
    }

    /**
     * 使用redisson实现分布式锁
     * <p>
     * 使用redis+lua脚本实现分布式锁
     * 1.setnx key val获取锁 setIfAbsent
     * 问题：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放
     * 解决：设置过期时间，自动释放锁。
     * 2.SET key value [EX seconds] [PX milliseconds] [NX|XX]
     * 问题：可能会释放其他服务器的锁. a线程的执行业务时间长于锁的过期时间, 导致锁过期失效.b线程获取锁,
     * a线程执行业务后调用del删除的是b线程的锁
     * 解决：setnx获取锁时，设置一个指定的唯一值（例如：uuid）；释放前获取这个值，判断是否自己的锁
     * 3.设置uuid
     * 问题：删除操作缺乏原子性(可能会释放其他服务器的锁)
     * 解决:使用lua脚本
     */
    @Override
    public void testLock() {
        System.out.println("==================testLock方法==================");
        RLock lock = redissonClient.getLock("lock");
        //500不能加L, 结果会有问题
        //如果有异常, 在500毫秒后会自动解锁
//        lock.lock(500, TimeUnit.MILLISECONDS);

        boolean flag = false;
        try {
            System.out.println(Thread.currentThread().getName() + " 尝试获取锁。。。");
            flag = lock.tryLock(100, 5, TimeUnit.SECONDS);
            System.out.println(Thread.currentThread().getName() + " 获取锁成功");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (flag) {
            try {
                // 查询redis中的num值
                String value = redisTemplate.opsForValue().get("num");
                // 没有该值return
                if (StringUtils.isEmpty(value)) {
                    return;
                }
                // 有值就转成成int
                int num = Integer.parseInt(value);
                // 把redis中的num值+1
                redisTemplate.opsForValue().set("num", String.valueOf(++num));
                System.out.println("处理业务逻辑。。。");
            } finally {
//                    lock.unlock();
            }
        }


    }

//    @Override
//    public void testLock() {
//        System.out.println("==================testLock方法==================");
//        RLock lock = redissonClient.getLock("lock");
//        //500不能加L, 结果会有问题
//        //如果有异常, 在500毫秒后会自动解锁
//        System.out.println(Thread.currentThread().getName()+" 尝试获取锁。。。");
//        lock.lock(5, TimeUnit.SECONDS);
//        System.out.println(Thread.currentThread().getName()+" 获取锁成功");
//        // 查询redis中的num值
//        String value = redisTemplate.opsForValue().get("num");
//        // 没有该值return
//        if (StringUtils.isEmpty(value)) {
//            return;
//        }
//        // 有值就转成成int
//        int num = Integer.parseInt(value);
//        // 把redis中的num值+1
//        redisTemplate.opsForValue().set("num", String.valueOf(++num));
//        System.out.println("处理业务逻辑。。。");
////        lock.unlock();
//    }

/**
 * 使用redis+lua脚本实现分布式锁
 * 1.setnx key val获取锁 setIfAbsent
 *      问题：setnx刚好获取到锁，业务逻辑出现异常，导致锁无法释放
 *      解决：设置过期时间，自动释放锁。
 * 2.SET key value [EX seconds] [PX milliseconds] [NX|XX]
 *      问题：可能会释放其他服务器的锁. a线程的执行业务时间长于锁的过期时间, 导致锁过期失效.b线程获取锁,
 *              a线程执行业务后调用del删除的是b线程的锁
 *      解决：setnx获取锁时，设置一个指定的唯一值（例如：uuid）；释放前获取这个值，判断是否自己的锁
 * 3.设置uuid
 *      问题：删除操作缺乏原子性(可能会释放其他服务器的锁)
 *      解决:使用lua脚本
 */
//    @Override
//    public void testLock() {
//        String uuid = UUID.randomUUID().toString();
//        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", uuid, 500,TimeUnit.MILLISECONDS);
//        if (flag) {
//            String num = redisTemplate.opsForValue().get("num");
//            if (StringUtils.isEmpty(num)) {
//                return;
//            }
//            int n = Integer.parseInt(num);
//            redisTemplate.opsForValue().set("num", String.valueOf(++n));
//            String lock = redisTemplate.opsForValue().get("lock");
////            if (uuid.equals(lock)) {
////                redisTemplate.delete("lock");
////            }
//            //上面替代成下面
//            //  定义一个lua 脚本
//            String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
//            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
//            redisScript.setScriptText(script);
//            redisScript.setResultType(Long.class);
//            redisTemplate.execute(redisScript, Arrays.asList("lock"), uuid);
//        } else {
//            try {
//                Thread.sleep(300);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            testLock();
//        }
//    }

//    @Override
//    public synchronized void testLock() {
//        // 查询redis中的num值
//        String value = redisTemplate.opsForValue().get("num");
//        // 没有该值return
//        if (StringUtils.isEmpty(value)){
//            return ;
//        }
//        // 有值就转成成int
//        int num = Integer.parseInt(value);
//        // 把redis中的num值+1
//        redisTemplate.opsForValue().set("num", String.valueOf(++num));
//    }
}

